/*
 * 
 *  This file is part of Sourceprojects.org Lycia Copyright 2010 noctarius
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 * 
 */
package org.sourceprojects.lycia.internal.configuration;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

import javax.xml.XMLConstants;

import org.sourceprojects.lycia.DefinitionBuilder;
import org.sourceprojects.lycia.ParserContext;
import org.sourceprojects.lycia.QualifiedName;
import org.sourceprojects.lycia.annotations.Tag;
import org.sourceprojects.lycia.annotations.TagParser;
import org.sourceprojects.lycia.annotations.TagTransformer;
import org.sourceprojects.lycia.exceptions.LyciaConfigurationException;
import org.sourceprojects.lycia.internal.logging.LyciaLoggerWrapper;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

/**
 * 
 * @author noctarius
 * @since 1.9.1
 */
public class TagConfigurationUtils {
	private static final LyciaLoggerWrapper LOGGER = LyciaLoggerWrapper
			.getLogger(TagConfigurationUtils.class);

	public static String extractTagExpression(Tag tag) {
		return tag.value().trim();
	}

	public static String extractTagNamespace(Tag tag) {
		return tag.namespace().trim();
	}

	public static boolean matchesElement(final Element element,
			final QualifiedName[] qNames) {

		if (element == null) {
			return false;
		}

		final QualifiedName qName = qNames[qNames.length - 1];
		final String localPart = qName.getLocalPart();
		final String namespacePart = qName.getNamespaceURI();

		if (localPart.equals(element.getLocalName())) {

			if ((namespacePart.equals(XMLConstants.NULL_NS_URI) && element
					.getNamespaceURI() == null)
					|| (namespacePart.equals(element.getNamespaceURI()))) {

				if (qNames.length == 1) {
					return true;
				}

				final Node parent = element.getParentNode();
				if (!(parent instanceof Element)) {
					return false;
				}

				final QualifiedName[] temp = new QualifiedName[qNames.length - 1];
				System.arraycopy(qNames, 0, temp, 0, temp.length);

				return matchesElement((Element) parent, temp);
			}
		}

		return false;
	}

	public static QualifiedName[] generateParentQNames(final String tag,
			final String namespaceURI) {

		final String[] tags = tag.split("\\\\");

		final List<QualifiedName> qNames = new ArrayList<QualifiedName>();
		for (final String t : tags) {
			if (t.contains("{")) {
				final String pNamespaceURI = t.substring(t.indexOf("{") + 1, t
						.indexOf("}"));
				final String pTag = t.substring(t.indexOf("}") + 1);

				qNames.add(new QualifiedName(pNamespaceURI, pTag));
			} else {
				qNames.add(new QualifiedName(namespaceURI, t));
			}
		}

		return qNames.toArray(new QualifiedName[qNames.size()]);
	}

	public static MethodDefinition[] findAnnotatedMethods(final Class<?> clazz) {
		boolean foundDefaultTagTransformer = false;
		boolean foundDefaultTagParser = false;

		final List<MethodDefinition> foundMethods = new ArrayList<MethodDefinition>();
		for (final Method method : clazz.getDeclaredMethods()) {
			if (Modifier.isStatic(method.getModifiers()))
				continue;

			if (method.isAnnotationPresent(TagParser.class)) {
				if (LOGGER.isDebugEnabled()) {
					LOGGER.debug("Found @TagParser annotation on method '"
							+ method + "'");
				}

				final TagParser annotation = method
						.getAnnotation(TagParser.class);
				final Tag[] tags = annotation.value();

				if (hasDefaultTag(tags)) {
					if (foundDefaultTagParser) {
						throw new LyciaConfigurationException(
								"Duplicate default TagParser found on class "
										+ clazz.getCanonicalName());
					} else {
						foundDefaultTagParser = true;
					}
				}

				if (LOGGER.isDebugEnabled()) {
					LOGGER.debug("Method '" + method
							+ "' qualified as legal TagParser method");
				}

				final MethodDefinition definition = new MethodDefinition(
						method, tags);
				foundMethods.add(definition);

			} else if (method.isAnnotationPresent(TagTransformer.class)) {
				if (LOGGER.isDebugEnabled()) {
					LOGGER.debug("Found @TagTransformer annotation on method '"
							+ method + "'");
				}

				final TagTransformer annotation = method
						.getAnnotation(TagTransformer.class);
				final Tag[] tags = annotation.value();

				if (hasDefaultTag(tags)) {
					if (foundDefaultTagTransformer) {
						throw new LyciaConfigurationException(
								"Duplicate default TagTransformer found on class "
										+ clazz.getCanonicalName());
					} else {
						foundDefaultTagTransformer = true;
					}
				}

				if (LOGGER.isDebugEnabled()) {
					LOGGER.debug("Method '" + method
							+ "' qualified as legal TagTransformer method");
				}

				final MethodDefinition definition = new MethodDefinition(
						method, tags);
				foundMethods.add(definition);
			}

		}

		return foundMethods.toArray(new MethodDefinition[foundMethods.size()]);
	}

	private static boolean hasDefaultTag(final Tag[] tags) {
		for (final Tag tag : tags) {
			if ("*".equals(tag.value()) && isNullXPathAnnotation(tag)) {
				return true;
			}
		}

		return false;
	}

	public static <B> Object[] generateParameter(final Method method,
			final Element element, final String localName,
			final QualifiedName parent, final ParserContext<B> parserContext,
			final DefinitionBuilder definitionBuilder) {
		final Object[] result = new Object[method.getParameterTypes().length];

		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug("Resolving parameters for method '" + method + "'");
		}

		switch (method.getParameterTypes().length) {
		case 3:
			result[0] = element;
			result[1] = parserContext;
			result[2] = definitionBuilder;
			break;

		case 4:
			result[0] = element;
			result[1] = localName;
			result[2] = parserContext;
			result[3] = definitionBuilder;
			break;

		case 5:
			result[0] = element;
			result[1] = localName;
			result[2] = parent;
			result[3] = parserContext;
			result[4] = definitionBuilder;
			break;
		default:
		}

		return result;
	}

	public static final boolean isNullXPathAnnotation(final Tag tag) {
		final org.sourceprojects.lycia.annotations.XPath xpath = tag.xpath();
		if (xpath.expression().equals(
				org.sourceprojects.lycia.annotations.XPath.NULL)
				&& xpath.namespaces().length == 0) {
			return true;
		}

		return false;
	}

	public static class MethodDefinition {
		private final Tag[] tags;
		private final Method method;

		public MethodDefinition(final Method method, final Tag... tags) {
			this.method = method;
			this.tags = tags;
		}

		public Tag[] getTags() {
			final Tag[] result = new Tag[tags.length];
			System.arraycopy(tags, 0, result, 0, tags.length);
			return result;
		}

		public Method getMethod() {
			return method;
		}

	}
}
